/**
 * Copyright (c) 2010 David Benson, Gaudenz Alder
 */
package com.mxgraph.io.graphml;

import com.mxgraph.model.mxCell;
import com.mxgraph.util.mxPoint;
import com.mxgraph.view.mxConnectionConstraint;
import com.mxgraph.view.mxGraph;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * Represents a Graph element in the GML Structure.
 */
public class mxGraphMlGraph {
    /**
     * Map with the vertex cells added in the addNode method.
     */
    private static HashMap<String, Object> cellsMap = new HashMap<String, Object>();

    private String id = "";

    private String edgedefault = "";

    private List<mxGraphMlNode> nodes = new ArrayList<mxGraphMlNode>();

    private List<mxGraphMlEdge> edges = new ArrayList<mxGraphMlEdge>();

    /**
     * Constructs a graph with id and edge default direction.
     *
     * @param id          Graph's ID
     * @param edgedefault Edge Default direction.("directed" or "undirected")
     */
    public mxGraphMlGraph(String id, String edgedefault) {
        this.id = id;
        this.edgedefault = edgedefault;
    }

    /**
     * Constructs an empty graph.
     */
    public mxGraphMlGraph() {
    }

    /**
     * Constructs a graph from a xml graph element.
     *
     * @param graphElement Xml graph element.
     */
    public mxGraphMlGraph(Element graphElement) {
        this.id = graphElement.getAttribute(mxGraphMlConstants.ID);
        this.edgedefault = graphElement
                .getAttribute(mxGraphMlConstants.EDGE_DEFAULT);

        //Adds node elements
        List<Element> nodeElements = mxGraphMlUtils.childsTags(graphElement,
                mxGraphMlConstants.NODE);

        for (Element nodeElem : nodeElements) {
            mxGraphMlNode node = new mxGraphMlNode(nodeElem);

            nodes.add(node);
        }

        //Adds edge elements
        List<Element> edgeElements = mxGraphMlUtils.childsTags(graphElement,
                mxGraphMlConstants.EDGE);

        for (Element edgeElem : edgeElements) {
            mxGraphMlEdge edge = new mxGraphMlEdge(edgeElem);

            if (edge.getEdgeDirected().equals("")) {
                if (edgedefault.equals(mxGraphMlConstants.EDGE_DIRECTED)) {
                    edge.setEdgeDirected("true");
                } else if (edgedefault.equals(mxGraphMlConstants.EDGE_UNDIRECTED)) {
                    edge.setEdgeDirected("false");
                }
            }

            edges.add(edge);
        }
    }

    /**
     * Adds the elements represented for this graph model into the given graph.
     *
     * @param graph  Graph where the elements will be located
     * @param parent Parent of the cells to be added.
     */
    public void addGraph(mxGraph graph, Object parent) {
        List<mxGraphMlNode> nodeList = getNodes();

        for (mxGraphMlNode node : nodeList) {
            addNode(graph, parent, node);
        }
        List<mxGraphMlEdge> edgeList = getEdges();

        for (mxGraphMlEdge edge : edgeList) {
            addEdge(graph, parent, edge);
        }
    }

    /**
     * Checks if the node has data elements inside.
     *
     * @param node Gml node element.
     * @return Returns <code>true</code> if the node has data elements inside.
     */
    public static boolean hasData(mxGraphMlNode node) {
        boolean ret = false;
        if (node.getNodeDataMap() == null) {
            ret = false;
        } else {
            ret = true;
        }
        return ret;
    }

    /**
     * Returns the data element inside the node that references to the key element
     * with name = KEY_NODE_NAME.
     *
     * @param node Gml Node element.
     * @return The required data. null if not found.
     */
    public static mxGraphMlData dataNodeKey(mxGraphMlNode node) {
        String keyId = "";
        HashMap<String, mxGraphMlKey> keyMap = mxGraphMlKeyManager.getInstance()
                .getKeyMap();

        for (mxGraphMlKey key : keyMap.values()) {
            if (key.getKeyName().equals(mxGraphMlConstants.KEY_NODE_NAME)) {
                keyId = key.getKeyId();
            }
        }

        mxGraphMlData data = null;
        HashMap<String, mxGraphMlData> nodeDataMap = node.getNodeDataMap();
        data = nodeDataMap.get(keyId);

        return data;
    }

    /**
     * Returns the data element inside the edge that references to the key element
     * with name = KEY_EDGE_NAME.
     *
     * @param edge Gml Edge element.
     * @return The required data. null if not found.
     */
    public static mxGraphMlData dataEdgeKey(mxGraphMlEdge edge) {
        String keyId = "";
        HashMap<String, mxGraphMlKey> keyMap = mxGraphMlKeyManager.getInstance()
                .getKeyMap();
        for (mxGraphMlKey key : keyMap.values()) {
            if (key.getKeyName().equals(mxGraphMlConstants.KEY_EDGE_NAME)) {
                keyId = key.getKeyId();
            }
        }

        mxGraphMlData data = null;
        HashMap<String, mxGraphMlData> nodeDataMap = edge.getEdgeDataMap();
        data = nodeDataMap.get(keyId);

        return data;
    }

    /**
     * Adds the vertex represented for the gml node into the graph with the given parent.
     *
     * @param graph  Graph where the vertex will be added.
     * @param parent Parent's cell.
     * @param node   Gml Node
     * @return The inserted Vertex cell.
     */
    private mxCell addNode(mxGraph graph, Object parent, mxGraphMlNode node) {
        mxCell v1;
        String id = node.getNodeId();

        mxGraphMlData data = dataNodeKey(node);

        if (data != null && data.getDataShapeNode() != null) {
            Double x = Double.valueOf(data.getDataShapeNode().getDataX());
            Double y = Double.valueOf(data.getDataShapeNode().getDataY());
            Double h = Double.valueOf(data.getDataShapeNode().getDataHeight());
            Double w = Double.valueOf(data.getDataShapeNode().getDataWidth());
            String label = data.getDataShapeNode().getDataLabel();
            String style = data.getDataShapeNode().getDataStyle();
            v1 = (mxCell) graph.insertVertex(parent, id, label, x, y, w, h,
                    style);
        } else {
            v1 = (mxCell) graph.insertVertex(parent, id, "", 0, 0, 100, 100);
        }

        cellsMap.put(id, v1);
        List<mxGraphMlGraph> graphs = node.getNodeGraph();

        for (mxGraphMlGraph gmlGraph : graphs) {
            gmlGraph.addGraph(graph, v1);
        }
        return v1;
    }

    /**
     * Returns the point represented for the port name.
     * The specials names North, NorthWest, NorthEast, East, West, South, SouthEast and SouthWest.
     * are accepted. Else, the values acepted follow the pattern "double,double".
     * where double must be in the range 0..1
     *
     * @param source Port Name.
     * @return point that represent the port value.
     */
    private static mxPoint portValue(String source) {
        mxPoint fromConstraint = null;

        if (source != null && !source.equals("")) {

            if (source.equals("North")) {
                fromConstraint = new mxPoint(0.5, 0);
            } else if (source.equals("South")) {
                fromConstraint = new mxPoint(0.5, 1);

            } else if (source.equals("East")) {
                fromConstraint = new mxPoint(1, 0.5);

            } else if (source.equals("West")) {
                fromConstraint = new mxPoint(0, 0.5);

            } else if (source.equals("NorthWest")) {
                fromConstraint = new mxPoint(0, 0);
            } else if (source.equals("SouthWest")) {
                fromConstraint = new mxPoint(0, 1);
            } else if (source.equals("SouthEast")) {
                fromConstraint = new mxPoint(1, 1);
            } else if (source.equals("NorthEast")) {
                fromConstraint = new mxPoint(1, 0);
            } else {
                try {
                    String[] s = source.split(",");
                    Double x = Double.valueOf(s[0]);
                    Double y = Double.valueOf(s[1]);
                    fromConstraint = new mxPoint(x, y);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return fromConstraint;
    }

    /**
     * Adds the edge represented for the gml edge into the graph with the given parent.
     *
     * @param graph  Graph where the vertex will be added.
     * @param parent Parent's cell.
     * @param edge   Gml Edge
     * @return The inserted edge cell.
     */
    private static mxCell addEdge(mxGraph graph, Object parent, mxGraphMlEdge edge) {
        //Get source and target vertex
        mxPoint fromConstraint = null;
        mxPoint toConstraint = null;
        Object source = cellsMap.get(edge.getEdgeSource());
        Object target = cellsMap.get(edge.getEdgeTarget());
        String sourcePort = edge.getEdgeSourcePort();
        String targetPort = edge.getEdgeTargetPort();

        fromConstraint = portValue(sourcePort);

        toConstraint = portValue(targetPort);

        mxGraphMlData data = dataEdgeKey(edge);

        String style = "";
        String label = "";

        if (data != null) {
            mxGraphMlShapeEdge shEdge = data.getDataShapeEdge();
            style = shEdge.getStyle();
            label = shEdge.getText();
        } else {
            style = edge.getEdgeStyle();
        }

        //Insert new edge.
        mxCell e = (mxCell) graph.insertEdge(parent, null, label, source,
                target, style);
        graph.setConnectionConstraint(e, source, true,
                new mxConnectionConstraint(fromConstraint, false));
        graph.setConnectionConstraint(e, target, false,
                new mxConnectionConstraint(toConstraint, false));
        return e;
    }

    public String getEdgedefault() {
        return edgedefault;
    }

    public void setEdgedefault(String edgedefault) {
        this.edgedefault = edgedefault;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public List<mxGraphMlNode> getNodes() {
        return nodes;
    }

    public void setNodes(List<mxGraphMlNode> node) {
        this.nodes = node;
    }

    public List<mxGraphMlEdge> getEdges() {
        return edges;
    }

    public void setEdges(List<mxGraphMlEdge> edge) {
        this.edges = edge;
    }

    /**
     * Checks if the graph has child nodes or edges.
     *
     * @return Returns <code>true</code> if the graph hasn't child nodes or edges.
     */
    public boolean isEmpty() {
        return nodes.size() == 0 && edges.size() == 0;
    }

    /**
     * Generates a Key Element from this class.
     *
     * @param document Document where the key Element will be inserted.
     * @return Returns the generated Elements.
     */
    public Element generateElement(Document document) {
        Element graph = document.createElement(mxGraphMlConstants.GRAPH);

        if (!id.equals("")) {
            graph.setAttribute(mxGraphMlConstants.ID, id);
        }
        if (!edgedefault.equals("")) {
            graph.setAttribute(mxGraphMlConstants.EDGE_DEFAULT, edgedefault);
        }

        for (mxGraphMlNode node : nodes) {
            Element nodeElement = node.generateElement(document);
            graph.appendChild(nodeElement);
        }

        for (mxGraphMlEdge edge : edges) {
            Element edgeElement = edge.generateElement(document);
            graph.appendChild(edgeElement);
        }

        return graph;
    }
}
